package erresp

import (
	"encoding/json"
	"fmt"

	"gitee.com/krio/go-utils/helper"
	"github.com/gogo/status"
)

type errNo struct {
	HTTPStatus int         `json:"-"`                // http状态码
	Code       int32       `json:"code"`             // 自定义状态码
	Message    string      `json:"message"`          // 响应描述
	Prompt     string      `json:"prompt,omitempty"` // 响应提示
	Data       interface{} `json:"data,omitempty"`   // 响应数据
}

var errNos = make(map[int32]Erresp)

func newErrNo(httpStatus int, code int32, message, prompt string, data interface{}) *errNo {
	return &errNo{
		HTTPStatus: httpStatus,
		Code:       code,
		Message:    message,
		Prompt:     prompt,
		Data:       data,
	}
}

type Erresp interface {
	Error() string
	GetHTTPStatus() int
	GetCode() int32
	GetMessage() string
	GetPrompt() string
	GetData() interface{}

	WithMessage(message string) Erresp
	WithPrompt(message string) Erresp
	WithData(data interface{}) Erresp
	ToString() string
}

func register(httpStatus int, code int32, message, prompt string) Erresp {
	if _, exist := errNos[code]; exist {
		panic(fmt.Sprintf("errno %d already exists", code))
	}
	e := newErrNo(httpStatus, code, message, prompt, nil)
	errNos[code] = e
	return newErrNo(httpStatus, code, message, prompt, nil)
}

func (e *errNo) Error() string {
	return e.ToString()
}

func (en *errNo) GetHTTPStatus() int {
	return en.HTTPStatus
}

func (en *errNo) GetCode() int32 {
	return en.Code
}

func (en *errNo) GetMessage() string {
	return en.Message
}

func (en *errNo) GetPrompt() string {
	return en.Prompt
}

func (en *errNo) GetData() interface{} {
	return en.Data
}

// 自定义响应信息
func (en *errNo) WithMessage(message string) Erresp {
	return newErrNo(en.HTTPStatus, en.Code, message, en.Prompt, en.Data)
}

// 自定义响应提示
func (en *errNo) WithPrompt(prompt string) Erresp {
	return newErrNo(en.HTTPStatus, en.Code, en.Message, prompt, en.Data)
}

// 追加响应数据
func (en *errNo) WithData(data interface{}) Erresp {
	return newErrNo(en.HTTPStatus, en.Code, en.Message, en.Prompt, data)
}

// ToString 返回 JSON 格式的错误详情
func (en *errNo) ToString() string {
	err := &errNo{
		HTTPStatus: en.HTTPStatus,
		Code:       en.Code,
		Message:    en.Message,
		Prompt:     en.Prompt,
		Data:       en.Data,
	}
	raw, _ := json.Marshal(err)

	return string(raw)
}

// Parse converts an interface to an ErrNo.
func Parse(err interface{}) Erresp {
	if err == nil {
		return nil
	}
	switch e := err.(type) {
	case Erresp:
		return e
	case error:
		// 获取gRPC的错误code
		if s, ok := status.FromError(e); ok {
			en := GetErrNo(int32(s.Code()))
			newEn := helper.Clone(en)
			return newEn.(Erresp)
		}
		return Unknown.WithMessage(e.Error())
	default:
		return Unknown
	}
}

// GetErrNo returns the ErrNo by code.
func GetErrNo(code int32) Erresp {
	if en, exist := errNos[code]; exist {
		return en
	}
	return Unknown
}
